/**
 * animated model
 * based on MD2 loader
 * by Philippe Ajoux (philippe.ajoux@gmail.com) 
 * also minor changes
 * @author John Sword
 * @version 2 - AS3 
 */

package engine.objects
{
	
	import engine.camera.Camera3D;
	import engine.geom.UV;
	import engine.geom.Vertex;
	import engine.materials.Material;
	import engine.materials.Wireframe;
	import engine.utils.Frame;
	
	import flash.events.Event;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.utils.getTimer;
	
	public class MD2 extends Object3D
	{

		public var fps:int = 6;

		/**
		 * Three kinds of animation sequences:
		 *  [1] Normal (sequential, just playing)
		 *  [2] Loop   (a loop)
		 *  [3] Stop   (stopped, not animating)
		 */
		public static const ANIM_NORMAL:int = 1;
		public static const ANIM_LOOP:int = 2;
		public static const ANIM_STOP:int = 4;
		
		/**
		 * Keep track of the current frame number and animation
		 */
		private var frames:Array = new Array();
		private var keyframe:int = 0;
		//private var iterator:Number = 5;
		private var interp:Number = 1;
		private var start:int, end:int, type:int;
		private var ctime:Number = 0, otime:Number = 0;
		private var animationList:Array = new Array();
		private var currentAnim:String = "stand"; // default animation

		// [internal] Used for loading the MD2 file
		private var file:String;
		private var loader:URLLoader;
		private var loadScale:Number;
		
		
		// [internal] Md2 file format has a bunch of header information
		// that is typically just read straight into a C-style struct, but
		// since this is not C =( we have to create variables for all of it.
		private var ident:int, version:int;
		private var skinwidth:int, skinheight:int;
		private var framesize:int;
		private var num_skins:int, num_vertices:int, num_st:int;
		private var num_tris:int, num_glcmds:int, num_frames:int;
		private var offset_skins:int, offset_st:int, offset_tris:int;
		private var offset_frames:int, offset_glcmds:int, offset_end:int;

		public function MD2 ( filename:String, m:Material = null )
		{
			isVisible = false;
			flipNormals = true;
			material = m;
			// optimal scale
			//this.scale = new Vector(4,4,4);
			file = filename;
			load(filename);
		}
		
		private function load ( filename:String ) : void
		{
			loader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, parse);
			
			try
			{
	            loader.load( new URLRequest( filename ) );
			}
			catch(e:Error)
			{
				trace("Error in loading MD2 file (" + filename + "): \n" + e.message + "\n" + e.getStackTrace());
			}
		}
		
		private function parse ( event:Event ) : void
		{
			var a:int, b:int, c:int, ta:int, tb:int, tc:int;
			var vertices:Array = super.oVertices;
			var faces:Array = super.faces;
			var i:int, uvs:Array = new Array();
			var data:ByteArray = loader.data;
			
			// Make sure to have this in Little Endian or you will hate you life.
			// At least I did the first time I did this for a while.
			data.endian = Endian.LITTLE_ENDIAN;
			
			// Read the header and make sure it is valid MD2 file
			readMd2Header(data);
			if (ident != 844121161 || version != 8)
				throw new Error("Error loading MD2 file (" + file + "): Not a valid MD2 file/bad version");
				
			// Vertice setup
			// 		Be sure to allocate memory for the vertices to the object
			//		These vertices will be updated each frame with the proper coordinates
			for (i = 0; i < num_vertices; i++)
				createVertex( new Vertex () );
			//for (i = 0; i < num_vertices; i++)
			//	tVertices.push( new Vertex() );
				
			// UV coordinates
			//		Load them!
			data.position = offset_st;
			for (i = 0; i < num_st; i++)
				uvs.push(new UV(data.readShort() / skinwidth, 1 - ( data.readShort() / skinheight) ));
			
			// Faces
			//		Creates the faces with the proper references to vertices
			//		NOTE: DO NOT change the order of the variable assignments here, 
			//			  or nothing will work.
			data.position = offset_tris;
			var faceuvs:Array = new Array();
			var m:Wireframe = new Wireframe ();
			for (i = 0; i < num_tris; i++)	{
				a = data.readUnsignedShort();
				b = data.readUnsignedShort();
				c = data.readUnsignedShort();
				ta = data.readUnsignedShort();
				tb = data.readUnsignedShort();
				tc = data.readUnsignedShort();
				
				createFace ( a, b, c, uvs[ta],uvs[tb],uvs[tc], material );
			}
			
			// Frame animation data
			//		This part is a little funky.
			data.position = offset_frames;
			readFrames(data);
			
			loader.close();
			
			isVisible = true;
			if ( interactive ) setInteractive();
			dispatchEvent ( new Event("LOAD") );
		}
		
		/**
		 * Reads in all the frames
		 */
		private function readFrames(data:ByteArray):void
		{
			var sx:Number, sy:Number, sz:Number;
			var tx:Number, ty:Number, tz:Number;
			var verts:Array, frame:Frame;
			var i:int, j:int, char:int;
			var x:Number,y:Number,z:Number;
			// rotate model to conform with engine coordinate system
			var xXrot:Number,yXrot:Number,zXrot:Number;
			var xZrot:Number,yZrot:Number,zZrot:Number;
			// set rotation values
			var rotX:Number = 90 * (Math.PI / 180);
			var rotZ:Number = rotX;
			//var rotZ:Number = 90 * (Math.PI / 180);
			// use a regular expression to remove frame numbers
			// and get the animation name
			// not a very elegant solution
			var pattern:RegExp = /[0-9]/gi;
			var animName:String = "";
			
			for (i = 0; i < num_frames; i++)
			{
				verts = new Array();
				frame = new Frame("", verts);
				
				sx = data.readFloat();
				sy = data.readFloat();
				sz = data.readFloat();
				
				tx = data.readFloat();
				ty = data.readFloat();
				tz = data.readFloat();
				
				for (j = 0; j < 16; j++)
					if ((char = data.readUnsignedByte()) != 0)
						frame.name += String.fromCharCode(char);
				
				animName = frame.name.replace(pattern, "");
				var animation:Object = animationList[animName];
				if ( animation == null ) 
				{
					var anim:Object = new Object();
					anim.name = animName;
					anim.start = i;
					anim.end = i;
					animationList[animName] = anim;
				} else {
					anim.end = i;
				}	
				
				// Note, the extra data.position++ in the for loop is there 
				// to skip over a byte that holds the "vertex normal index"
				for (j = 0; j < num_vertices; j++, data.position++)
				{
					x = ((sx * data.readUnsignedByte()) + tx) * 4;
					y = ((sy * data.readUnsignedByte()) + ty) * 4;
					z = ((sz * data.readUnsignedByte()) + tz) * 4;
					
					//rotate around Z-axis
					xZrot = x;
					yZrot = Math.cos(rotX)*y - Math.sin(rotX)*z;
					zZrot = Math.sin(rotX)*y + Math.cos(rotX)*z;
					// then rotate around X-axis
					xXrot = Math.cos(rotZ)*xZrot - Math.sin(rotZ)*zZrot;
					yXrot = yZrot;
					zXrot = Math.sin(rotZ)*xZrot + Math.cos(rotZ)*zZrot;
					
					verts.push(new Vertex ( xXrot,yXrot,zXrot ));
				}
	
				frames.push(frame);
			}
			
		}
		
		/**
		 * Reads in all that MD2 Header data that is declared as private variables.
		 * I know its a lot, and it looks ugly, but only way to do it in Flash
		 */
		private function readMd2Header(data:ByteArray):void
		{
			ident = data.readInt();
			version = data.readInt();
			skinwidth = data.readInt();
			skinheight = data.readInt();
			framesize = data.readInt();
			num_skins = data.readInt();
			num_vertices = data.readInt();
			num_st = data.readInt();
			num_tris = data.readInt();
			num_glcmds = data.readInt();
			num_frames = data.readInt();
			offset_skins = data.readInt();
			offset_st = data.readInt();
			offset_tris = data.readInt();
			offset_frames = data.readInt();
			offset_glcmds = data.readInt();
			offset_end = data.readInt();
		}
		
		public override function project ( cam:Camera3D, occlusion:Boolean = false ) : int
		{
			
			ctime = getTimer();
			
			var dst:Vertex, a:Vertex, b:Vertex;
			var cframe:Frame, nframe:Frame;
			
			if ( keyframe == frames.length ) keyframe = 0;
			
			cframe = frames[keyframe];
			nframe = frames[(keyframe + 1) % frames.length];
			
			//var len:int = oVertices.length;
			//var i:int;
			//for (i = 0; i < len; i++)
			//{
			var i:int = oVertices.length;
			while ( --i > -1 )
			{
				dst = oVertices[i];
				a = cframe.vertices[i];
				b = nframe.vertices[i];
				dst.x = a.x + interp * (b.x - a.x);
				dst.y = a.y + interp * (b.y - a.y);
				dst.z = a.z + interp * (b.z - a.z); 
			}

			// Update the timer part, to get time based animation
			
			if (type != ANIM_STOP)
			{
				interp += fps * (ctime - otime) / 1000;
				if (interp >= 1)
				{
					if (type == ANIM_LOOP && keyframe + 1 == end)
						keyframe = start;
					else
						keyframe++;
					interp = 0;
				}
			}
			otime = ctime;
			
			/*
			iterator ++
			if ( iterator > frameSpeed ) {
				keyframe ++;
				iterator = 0;
			}
			if ( keyframe == frames.length ) keyframe = 0;
			var frame:Frame = frames[keyframe];
			oVertices = frame.vertices;
			*/
			if ( type != ANIM_STOP ) renderMe = true;
			return super.project( cam, occlusion );
		}
		
		public function gotoAndPlay ( frame:int ) : void
		{
			keyframe = frame;
			type = ANIM_NORMAL;
		}
		
		public function play ( anim:String ) : Boolean
		{
			if ( !currentAnim || currentAnim == anim ) return false;
			var animation:Object = animationList[anim];
			if ( animation ) 
			{
				loop ( animation.start, animation.end );
				currentAnim = anim;
				return true;
			}
			return false;
		}
		
		public function loop( start:int, end:int ) : void
		{
			this.start = (start % frames.length);
			keyframe = start;
			this.end = (end % frames.length);
			type = ANIM_LOOP;
		}
		
		public function stop():void
		{
			type = ANIM_STOP;
		}
		
		public function gotoAndStop(frame:int):void
		{
			keyframe = frame;
			type = ANIM_STOP;
		}

	}
}